{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit ScreenSaver;

interface

uses Types, AHMTypes, Graphic;

const
  MILLISECONDS_PER_SEC = 1000;
  SAVER_UPDATE_INTERVAL = 5000; // Time between updates in ms
  SAVER_ROTATION_SPEED = 0.1;
  SAVER_IMAGE_FILE = 'screensaver.png';
  SAVER_IMAGE_EMPTY = 'blank';

type
  TAHMScreenSaver = class
  private
    FIdle: Boolean;
    FImage: TAHMGraphic;
    FScreenSaverDelay: Integer;
    FDelayMillisecs: TAHMTimestamp;
    FImageX: Single;
    FImageY: Single;
    FUpdateTimestamp: TAHMTimestamp;
    FActivityTimestamp: TAHMTimestamp;
    FRotation: Single;
  protected
    procedure SetScreenSaverDelay(Seconds: Integer);
    procedure Initialise;
    procedure ChangePosition;
  public
    constructor Create;
    destructor Destroy; override;
    property ScreenSaverDelay: Integer read FScreenSaverDelay write SetScreenSaverDelay;
    property Idle: Boolean read FIdle;
    function DidSomething: Boolean;
    function CheckIdle: Boolean;
    procedure GoIdle;
    procedure Render;
  end;


implementation

uses Windows, MMSystem, Graphics, ApplicationManager, Screen, Canvas, Utilities;


constructor TAHMScreenSaver.Create;
begin
  inherited Create;

  FImage := TAHMGraphic.Create;

  // Set initial timestamp for last activity & movement
  FActivityTimestamp := timeGetTime;
  FUpdateTimestamp := timeGetTime;

  // Disable system screen saver
  DisableSystemScreenSaver;
end;

destructor TAHMScreenSaver.Destroy;
begin
  FImage.Free;

  // Re-enable system screen saver
  EnableSystemScreenSaver;

  inherited Destroy;
end;

procedure TAHMScreenSaver.SetScreenSaverDelay(Seconds: Integer);
begin
  // Convert delay in seconds to milliseconds
  FScreenSaverDelay := Seconds;
  FDelayMillisecs := Seconds * MILLISECONDS_PER_SEC;
end;

procedure TAHMScreenSaver.Initialise;
var
  sFilename: String;
begin
  // Don't bother loading screen saver image if we're disabled
  if FScreenSaverDelay = 0 then Exit;

  // Determine screen saver image file to load
  with ApplicationManagerInstance do
  begin
    sFilename := PreferencesManager.ScreenSaverFile;
    if sFilename = '' then sFilename := UIManager.ResourceURL + SAVER_IMAGE_FILE;
  end;

  // Attempt to load screensaver graphic - graphic will cache as necessary
  FImage.LoadFromFile(sFilename);
end;

procedure TAHMScreenSaver.ChangePosition;
var
  xmax, ymax: Integer;
begin
  // Clip image to screen boundaries
  xmax := ScreenInstance.PhysicalWidth - FImage.ImageWidth;
  ymax := ScreenInstance.PhysicalHeight - FImage.ImageHeight;

  // Generate random coordinates
  FImageX := Random(xmax) / ScreenInstance.PhysicalWidth;
  FImageY := Random(ymax) / ScreenInstance.PhysicalHeight;
end;

function TAHMScreenSaver.DidSomething: Boolean;
begin
  // Set default result - no change
  Result := False;

  // Don't bother with this if screen saver is disabled
  if FScreenSaverDelay = 0 then Exit;

  // Return true if we just woke up
  if FIdle then Result := True;

  // Reset inactivity timer and clear idle flag
  FActivityTimestamp := timeGetTime;
  FIdle := False;
end;

function TAHMScreenSaver.CheckIdle: Boolean;
var
  timestamp: TAHMTimestamp;
begin
  // Set default result - not idle (no screen saver)
  Result := False;

{$IFDEF DESIGNER}
  // Check screensaver isn't disabled in designer preferences
  with ApplicationManagerInstance.PreferencesManager.DesignerPrefs do
    if not MediateScreenSaver then Exit;
{$ENDIF}

  // Don't bother with this if screen saver is disabled
  if FScreenSaverDelay = 0 then Exit;

  // If we're already idle then return status otherwise check elapsed time
  if not FIdle then
  begin
    timestamp := timeGetTime;
    if (timestamp > (FActivityTimestamp + FDelayMillisecs)) or (timestamp < FActivityTimestamp) then
    begin
      // We've waited long enough, kick in the screensaver
      FIdle := True;
    end;
  end;

  Result := FIdle;
end;

procedure TAHMScreenSaver.GoIdle;
begin
  // If screen saver is enabled then immediately display id (timeout delay)  
  if FScreenSaverDelay <> 0 then FIdle := True;
end;
    
procedure TAHMScreenSaver.Render;
var
  timestamp: TAHMTimestamp;
  Rect: TAHMRectF;
begin
  // Don't bother with this if screen saver is disabled or not running
  if (FScreenSaverDelay = 0) or not FIdle then Exit;

  // Initialise screen saver graphic image
  Initialise;

  // Is it time to move our image yet?
  timestamp := timeGetTime;
  if (timestamp > (FUpdateTimestamp + SAVER_UPDATE_INTERVAL)) or (timestamp < FUpdateTimestamp) then
  begin
    // Generate new position for graphic and save timestamp
    ChangePosition;
    FUpdateTimestamp := timestamp;
  end;

  // Apply image rotation effect about screen center
  FRotation := FRotation + SAVER_ROTATION_SPEED;
  if FRotation > 360 then FRotation := 0.0;
  with ScreenInstance do
  begin
    Viewport.Canvas.UnlockRotation;
    Viewport.Canvas.SetRotation(0, 0, FRotation, 0.5, 0.5);
  end;

  // Draw image at our current x/y coordinates
  with FImage do
  begin
    if not Loaded then Exit; // don't render if image failed to load
    Rect := MakeRectF(FImageX, FImageY, Width, Height);
    DrawGraphic(ImageRect, Rect, nil, nil, 1.0, 1.0, False, False);
  end;
end;


end.
